export default function createToast({
  BaseComponent,
  CLASS_PREFIX,
  DATA_PREFIX_BASE,
  DATA_PREFIX,
  enableDismissTrigger,
  EventHandler,
  reflow,
}) {
  /**
   * Constants
   */

  const NAME = 'toast'
  const DATA_KEY = `${DATA_PREFIX_BASE}.toast`
  const EVENT_KEY = `.${DATA_KEY}`

  const EVENT_MOUSEOVER = `mouseover${EVENT_KEY}`
  const EVENT_MOUSEOUT = `mouseout${EVENT_KEY}`
  const EVENT_FOCUSIN = `focusin${EVENT_KEY}`
  const EVENT_FOCUSOUT = `focusout${EVENT_KEY}`
  const EVENT_HIDE = `hide${EVENT_KEY}`
  const EVENT_HIDDEN = `hidden${EVENT_KEY}`
  const EVENT_SHOW = `show${EVENT_KEY}`
  const EVENT_SHOWN = `shown${EVENT_KEY}`

  const CLASS_NAME_FADE = `${CLASS_PREFIX}fade`
  const CLASS_NAME_HIDE = `${CLASS_PREFIX}hide` // @deprecated - kept here only for backwards compatibility
  const CLASS_NAME_SHOW = `${CLASS_PREFIX}show`
  const CLASS_NAME_SHOWING = `${CLASS_PREFIX}showing`

  const DefaultType = {
    animation: 'boolean',
    autohide: 'boolean',
    delay: 'number',
  }

  const Default = {
    animation: true,
    autohide: true,
    delay: 5000,
  }

  /**
   * Class definition
   */

  class Toast extends BaseComponent {
    constructor(element, config) {
      super(element, config)

      this._timeout = null
      this._hasMouseInteraction = false
      this._hasKeyboardInteraction = false
      this._setListeners()
    }

    // Getters
    static get Default() {
      return Default
    }

    static get DefaultType() {
      return DefaultType
    }

    static get NAME() {
      return NAME
    }

    // Public
    show() {
      const showEvent = EventHandler.trigger(this._element, EVENT_SHOW)

      if (showEvent.defaultPrevented) {
        return
      }

      this._clearTimeout()

      if (this._config.animation) {
        this._element.classList.add(CLASS_NAME_FADE)
      }

      const complete = () => {
        this._element.classList.remove(CLASS_NAME_SHOWING)
        EventHandler.trigger(this._element, EVENT_SHOWN)

        this._maybeScheduleHide()
      }

      this._element.classList.remove(CLASS_NAME_HIDE) // @deprecated
      reflow(this._element)
      this._element.classList.add(CLASS_NAME_SHOW, CLASS_NAME_SHOWING)

      this._queueCallback(complete, this._element, this._config.animation)
    }

    hide() {
      if (!this.isShown()) {
        return
      }

      const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)

      if (hideEvent.defaultPrevented) {
        return
      }

      const complete = () => {
        this._element.classList.add(CLASS_NAME_HIDE) // @deprecated
        this._element.classList.remove(CLASS_NAME_SHOWING, CLASS_NAME_SHOW)
        EventHandler.trigger(this._element, EVENT_HIDDEN)
      }

      this._element.classList.add(CLASS_NAME_SHOWING)
      this._queueCallback(complete, this._element, this._config.animation)
    }

    dispose() {
      this._clearTimeout()

      if (this.isShown()) {
        this._element.classList.remove(CLASS_NAME_SHOW)
      }

      super.dispose()
    }

    isShown() {
      return this._element.classList.contains(CLASS_NAME_SHOW)
    }

    // Private

    _maybeScheduleHide() {
      if (!this._config.autohide) {
        return
      }

      if (this._hasMouseInteraction || this._hasKeyboardInteraction) {
        return
      }

      this._timeout = setTimeout(() => {
        this.hide()
      }, this._config.delay)
    }

    _onInteraction(event, isInteracting) {
      switch (event.type) {
        case 'mouseover':
        case 'mouseout': {
          this._hasMouseInteraction = isInteracting
          break
        }

        case 'focusin':
        case 'focusout': {
          this._hasKeyboardInteraction = isInteracting
          break
        }

        default: {
          break
        }
      }

      if (isInteracting) {
        this._clearTimeout()
        return
      }

      const nextElement = event.relatedTarget
      if (
        this._element === nextElement ||
        this._element.contains(nextElement)
      ) {
        return
      }

      this._maybeScheduleHide()
    }

    _setListeners() {
      EventHandler.on(this._element, EVENT_MOUSEOVER, (event) =>
        this._onInteraction(event, true),
      )
      EventHandler.on(this._element, EVENT_MOUSEOUT, (event) =>
        this._onInteraction(event, false),
      )
      EventHandler.on(this._element, EVENT_FOCUSIN, (event) =>
        this._onInteraction(event, true),
      )
      EventHandler.on(this._element, EVENT_FOCUSOUT, (event) =>
        this._onInteraction(event, false),
      )
    }

    _clearTimeout() {
      clearTimeout(this._timeout)
      this._timeout = null
    }

    // Static
    static jQueryInterface(config) {
      return this.each(function () {
        const data = Toast.getOrCreateInstance(this, config)

        if (typeof config === 'string') {
          if (typeof data[config] === 'undefined') {
            throw new TypeError(`No method named "${config}"`)
          }

          data[config](this)
        }
      })
    }
  }

  /**
   * Data API implementation
   */

  enableDismissTrigger(Toast)

  return Toast
}
